"""
Provides factory methods to assemble the Galaxy web application
"""
import atexit
import logging
import sys
import threading
import traceback

from paste import httpexceptions

import galaxy.app
import galaxy.datatypes.registry
import galaxy.model
import galaxy.model.mapping
import galaxy.web.framework
import galaxy.web.framework.webapp
from galaxy import util
from galaxy.util import asbool
from galaxy.util.properties import load_app_properties
from galaxy.webapps.util import (
    build_template_error_formatters,
    MiddlewareWrapUnsupported,
    wrap_if_allowed,
    wrap_if_allowed_or_fail
)

log = logging.getLogger(__name__)


class GalaxyWebApplication(galaxy.web.framework.webapp.WebApplication):
    pass


def app_factory(global_conf, load_app_kwds={}, **kwargs):
    """
    Return a wsgi application serving the root object
    """
    kwargs = load_app_properties(
        kwds=kwargs,
        **load_app_kwds
    )
    # Create the Galaxy application unless passed in
    if 'app' in kwargs:
        app = kwargs.pop('app')
        galaxy.app.app = app
    else:
        try:
            app = galaxy.app.UniverseApplication(global_conf=global_conf, **kwargs)
            galaxy.app.app = app
        except Exception:
            traceback.print_exc()
            sys.exit(1)
    # Call app's shutdown method when the interpeter exits, this cleanly stops
    # the various Galaxy application daemon threads
    app.application_stack.register_postfork_function(atexit.register, app.shutdown)
    # Create the universe WSGI application
    webapp = GalaxyWebApplication(app, session_cookie='galaxysession', name='galaxy')

    # STANDARD CONTROLLER ROUTES
    webapp.add_ui_controllers('galaxy.webapps.galaxy.controllers', app)
    # Force /history to go to view of current
    webapp.add_route('/history', controller='history', action='view')
    webapp.add_route('/history/view/{id}', controller='history', action='view')

    # Force /activate to go to the controller
    webapp.add_route('/activate', controller='user', action='activate')

    # Authentication endpoints.
    webapp.add_route('/authnz/', controller='authnz', action='index', provider=None)
    webapp.add_route('/authnz/{provider}/login', controller='authnz', action='login', provider=None)
    webapp.add_route('/authnz/{provider}/callback', controller='authnz', action='callback', provider=None)
    webapp.add_route('/authnz/{provider}/disconnect', controller='authnz', action='disconnect', provider=None)

    # These two routes handle our simple needs at the moment
    webapp.add_route('/async/{tool_id}/{data_id}/{data_secret}', controller='async', action='index', tool_id=None, data_id=None, data_secret=None)
    webapp.add_route('/{controller}/{action}', action='index')
    webapp.add_route('/{action}', controller='root', action='index')

    # allow for subdirectories in extra_files_path
    webapp.add_route('/datasets/{dataset_id}/display/{filename:.+?}', controller='dataset', action='display', dataset_id=None, filename=None)
    webapp.add_route('/datasets/{dataset_id}/{action}/{filename}', controller='dataset', action='index', dataset_id=None, filename=None)
    webapp.add_route('/display_application/{dataset_id}/{app_name}/{link_name}/{user_id}/{app_action}/{action_param}/{action_param_extra:.+?}',
                     controller='dataset', action='display_application', dataset_id=None, user_id=None,
                     app_name=None, link_name=None, app_action=None, action_param=None, action_param_extra=None)
    webapp.add_route('/u/{username}/d/{slug}/{filename}', controller='dataset', action='display_by_username_and_slug', filename=None)
    webapp.add_route('/u/{username}/p/{slug}', controller='page', action='display_by_username_and_slug')
    webapp.add_route('/u/{username}/h/{slug}', controller='history', action='display_by_username_and_slug')
    webapp.add_route('/u/{username}/w/{slug}', controller='workflow', action='display_by_username_and_slug')
    webapp.add_route('/u/{username}/w/{slug}/{format}', controller='workflow', action='display_by_username_and_slug')
    webapp.add_route('/u/{username}/v/{slug}', controller='visualization', action='display_by_username_and_slug')

    # TODO: Refactor above routes into external method to allow testing in
    # isolation as well.
    populate_api_routes(webapp, app)

    # CLIENTSIDE ROUTES
    # The following are routes that are handled completely on the clientside.
    # The following routes don't bootstrap any information, simply provide the
    # base analysis interface at which point the application takes over.

    webapp.add_client_route('/admin/data_tables', 'admin')
    webapp.add_client_route('/admin/users', 'admin')
    webapp.add_client_route('/admin/roles', 'admin')
    webapp.add_client_route('/admin/forms', 'admin')
    webapp.add_client_route('/admin/groups', 'admin')
    webapp.add_client_route('/admin/repositories', 'admin')
    webapp.add_client_route('/admin/tool_versions', 'admin')
    webapp.add_client_route('/admin/quotas', 'admin')
    webapp.add_client_route('/admin/form/{form_id}', 'admin')
    webapp.add_client_route('/admin/api_keys', 'admin')
    webapp.add_client_route('/tours')
    webapp.add_client_route('/tours/{tour_id}')
    webapp.add_client_route('/user')
    webapp.add_client_route('/user/{form_id}')
    webapp.add_client_route('/openids/list')
    webapp.add_client_route('/visualizations')
    webapp.add_client_route('/visualizations/edit')
    webapp.add_client_route('/visualizations/list_published')
    webapp.add_client_route('/visualizations/list')
    webapp.add_client_route('/pages/list')
    webapp.add_client_route('/pages/list_published')
    webapp.add_client_route('/pages/create')
    webapp.add_client_route('/pages/edit')
    webapp.add_client_route('/histories/citations')
    webapp.add_client_route('/histories/list')
    webapp.add_client_route('/histories/import')
    webapp.add_client_route('/histories/list_published')
    webapp.add_client_route('/histories/list_shared')
    webapp.add_client_route('/histories/rename')
    webapp.add_client_route('/histories/permissions')
    webapp.add_client_route('/histories/view')
    webapp.add_client_route('/histories/show_structure')
    webapp.add_client_route('/datasets/list')
    webapp.add_client_route('/datasets/edit')
    webapp.add_client_route('/datasets/error')
    webapp.add_client_route('/workflows/list')
    webapp.add_client_route('/workflows/list_published')
    webapp.add_client_route('/workflows/create')
    webapp.add_client_route('/workflows/run')
    webapp.add_client_route('/workflows/import')
    webapp.add_client_route('/custom_builds')

    # ==== Done
    # Indicate that all configuration settings have been provided
    webapp.finalize_config()

    # Wrap the webapp in some useful middleware
    if kwargs.get('middleware', True):
        webapp = wrap_in_middleware(webapp, global_conf, app.application_stack, **kwargs)
    if asbool(kwargs.get('static_enabled', True)):
        webapp = wrap_if_allowed(webapp, app.application_stack, wrap_in_static,
                                 args=(global_conf,),
                                 kwargs=dict(plugin_frameworks=[app.visualizations_registry], **kwargs))
    # Close any pooled database connections before forking
    try:
        galaxy.model.mapping.metadata.bind.dispose()
    except Exception:
        log.exception("Unable to dispose of pooled galaxy model database connections.")
    try:
        # This model may not actually be bound.
        if galaxy.model.tool_shed_install.mapping.metadata.bind:
            galaxy.model.tool_shed_install.mapping.metadata.bind.dispose()
    except Exception:
        log.exception("Unable to dispose of pooled toolshed install model database connections.")

    app.application_stack.register_postfork_function(postfork_setup)

    for th in threading.enumerate():
        if th.is_alive():
            log.debug("Prior to webapp return, Galaxy thread %s is alive.", th)
    # Return
    return webapp


def uwsgi_app():
    return galaxy.web.framework.webapp.build_native_uwsgi_app(app_factory, "galaxy")


# For backwards compatibility
uwsgi_app_factory = uwsgi_app


def postfork_setup():
    from galaxy.app import app
    app.control_worker.bind_and_start()
    app.application_stack.log_startup()


def populate_api_routes(webapp, app):
    webapp.add_api_controllers('galaxy.webapps.galaxy.api', app)

    valid_history_contents_types = [
        'dataset',
        'dataset_collection',
    ]

    # Accesss HDA details via histories/{history_id}/contents/datasets/{hda_id}
    webapp.mapper.resource("content_typed",
                           "{type:%s}s" % "|".join(valid_history_contents_types),
                           name_prefix="history_",
                           controller='history_contents',
                           path_prefix='/api/histories/{history_id}/contents',
                           parent_resources=dict(member_name='history', collection_name='histories'))
    # Legacy access to HDA details via histories/{history_id}/contents/{hda_id}
    webapp.mapper.resource('content',
                           'contents',
                           controller='history_contents',
                           name_prefix='history_',
                           path_prefix='/api/histories/{history_id}',
                           parent_resources=dict(member_name='history', collection_name='histories'))
    webapp.mapper.connect("history_contents_display",
                          "/api/histories/{history_id}/contents/{history_content_id}/display",
                          controller="datasets",
                          action="display",
                          conditions=dict(method=["GET"]))
    webapp.mapper.connect("history_contents_metadata_file",
                          "/api/histories/{history_id}/contents/{history_content_id}/metadata_file",
                          controller="datasets",
                          action="get_metadata_file",
                          conditions=dict(method=["GET"]))
    webapp.mapper.resource('user',
                           'users',
                           controller='group_users',
                           name_prefix='group_',
                           path_prefix='/api/groups/{group_id}',
                           parent_resources=dict(member_name='group', collection_name='groups'))
    webapp.mapper.resource('role',
                           'roles',
                           controller='group_roles',
                           name_prefix='group_',
                           path_prefix='/api/groups/{group_id}',
                           parent_resources=dict(member_name='group', collection_name='groups'))
    _add_item_tags_controller(webapp,
                              name_prefix="history_content_",
                              path_prefix='/api/histories/{history_id}/contents/{history_content_id}')
    webapp.mapper.connect('/api/histories/published', action='published', controller="histories", conditions=dict(method=["GET"]))
    webapp.mapper.connect('/api/histories/shared_with_me', action='shared_with_me', controller="histories")
    _add_item_tags_controller(webapp,
                              name_prefix="history_",
                              path_prefix='/api/histories/{history_id}')
    _add_item_tags_controller(webapp,
                              name_prefix="workflow_",
                              path_prefix='/api/workflows/{workflow_id}')
    _add_item_annotation_controller(webapp,
                                    name_prefix="history_content_",
                                    path_prefix='/api/histories/{history_id}/contents/{history_content_id}')
    _add_item_annotation_controller(webapp,
                                    name_prefix="history_",
                                    path_prefix='/api/histories/{history_id}')
    _add_item_annotation_controller(webapp,
                                    name_prefix="workflow_",
                                    path_prefix='/api/workflows/{workflow_id}')
    _add_item_provenance_controller(webapp,
                                    name_prefix="history_content_",
                                    path_prefix='/api/histories/{history_id}/contents/{history_content_id}')

    webapp.mapper.resource('dataset', 'datasets', path_prefix='/api')
    webapp.mapper.resource('tool_data', 'tool_data', path_prefix='/api')
    webapp.mapper.connect('/api/tool_data/{id:.+?}/fields/{value:.+?}/files/{path:.+?}', action='download_field_file', controller="tool_data")
    webapp.mapper.connect('/api/tool_data/{id:.+?}/fields/{value:.+?}', action='show_field', controller="tool_data")
    webapp.mapper.connect('/api/tool_data/{id:.+?}/reload', action='reload', controller="tool_data")
    webapp.mapper.resource('dataset_collection', 'dataset_collections', path_prefix='/api/')
    webapp.mapper.resource('form', 'forms', path_prefix='/api')
    webapp.mapper.resource('role', 'roles', path_prefix='/api')
    webapp.mapper.resource('upload', 'uploads', path_prefix='/api')
    webapp.mapper.connect('/api/ftp_files', controller='remote_files')
    webapp.mapper.resource('remote_file', 'remote_files', path_prefix='/api')
    webapp.mapper.resource('group', 'groups', path_prefix='/api')
    webapp.mapper.resource_with_deleted('quota', 'quotas', path_prefix='/api')

    webapp.mapper.connect('get_custom_builds_metadata',
                          '/api/histories/{id}/custom_builds_metadata',
                          controller='histories',
                          action='get_custom_builds_metadata',
                          conditions=dict(method=["GET"]))

    # =======================
    # ====== TOOLS API ======
    # =======================

    webapp.mapper.connect('/api/tools/fetch', action='fetch', controller='tools', conditions=dict(method=["POST"]))
    webapp.mapper.connect('/api/tools/all_requirements', action='all_requirements', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/build', action='build', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/reload', action='reload', controller="tools")
    webapp.mapper.connect('/api/tools/tests_summary', action='tests_summary', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/test_data_path', action='test_data_path', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/test_data', action='test_data', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/diagnostics', action='diagnostics', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/citations', action='citations', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/download', action='download', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/requirements', action='requirements', controller="tools")
    webapp.mapper.connect('/api/tools/{id:.+?}/install_dependencies', action='install_dependencies', controller="tools", conditions=dict(method=["POST"]))
    webapp.mapper.connect('/api/tools/{id:.+?}/dependencies', action='install_dependencies', controller="tools", conditions=dict(method=["POST"]))
    webapp.mapper.connect('/api/tools/{id:.+?}/dependencies', action='uninstall_dependencies', controller="tools", conditions=dict(method=["DELETE"]))
    webapp.mapper.connect('/api/tools/{id:.+?}/build_dependency_cache', action='build_dependency_cache', controller="tools", conditions=dict(method=["POST"]))
    webapp.mapper.connect('/api/tools/{id:.+?}', action='show', controller="tools")
    webapp.mapper.resource('tool', 'tools', path_prefix='/api')

    webapp.mapper.connect('/api/dependency_resolvers/clean', action="clean", controller="tool_dependencies", conditions=dict(method=["POST"]))
    webapp.mapper.connect('/api/dependency_resolvers/dependency', action="manager_dependency", controller="tool_dependencies", conditions=dict(method=["GET"]))
    webapp.mapper.connect('/api/dependency_resolvers/dependency', action="install_dependency", controller="tool_dependencies", conditions=dict(method=["POST"]))
    webapp.mapper.connect('/api/dependency_resolvers/requirements', action="manager_requirements", controller="tool_dependencies")
    webapp.mapper.connect('/api/dependency_resolvers/{id}/clean', action="clean", controller="tool_dependencies", conditions=dict(method=["POST"]))
    webapp.mapper.connect('/api/dependency_resolvers/{id}/dependency', action="resolver_dependency", controller="tool_dependencies", conditions=dict(method=["GET"]))
    webapp.mapper.connect('/api/dependency_resolvers/{id}/dependency', action="install_dependency", controller="tool_dependencies", conditions=dict(method=["POST"]))
    webapp.mapper.connect('/api/dependency_resolvers/{id}/requirements', action="resolver_requirements", controller="tool_dependencies")
    webapp.mapper.resource('dependency_resolver', 'dependency_resolvers', controller="tool_dependencies", path_prefix='api')

    webapp.mapper.resource_with_deleted('user', 'users', path_prefix='/api')
    webapp.mapper.resource('genome', 'genomes', path_prefix='/api')
    webapp.mapper.connect('/api/genomes/{id}/indexes', controller='genomes', action='indexes')
    webapp.mapper.connect('/api/genomes/{id}/sequences', controller='genomes', action='sequences')
    webapp.mapper.resource('visualization', 'visualizations', path_prefix='/api')
    webapp.mapper.resource('plugins', 'plugins', path_prefix='/api')
    webapp.mapper.connect('/api/workflows/build_module', action='build_module', controller="workflows")
    webapp.mapper.connect('/api/workflows/menu', action='get_workflow_menu', controller="workflows", conditions=dict(method=["GET"]))
    webapp.mapper.connect('/api/workflows/menu', action='set_workflow_menu', controller="workflows", conditions=dict(method=["PUT"]))
    webapp.mapper.resource('workflow', 'workflows', path_prefix='/api')
    webapp.mapper.resource_with_deleted('history', 'histories', path_prefix='/api')
    webapp.mapper.connect('/api/histories/{history_id}/citations', action='citations', controller="histories")
    webapp.mapper.connect(
        'dynamic_tool_confs',
        '/api/configuration/dynamic_tool_confs',
        controller="configuration",
        action="dynamic_tool_confs"
    )
    webapp.mapper.connect(
        'tool_lineages',
        '/api/configuration/tool_lineages',
        controller="configuration",
        action="tool_lineages"
    )
    webapp.mapper.connect(
        '/api/configuration/toolbox',
        controller="configuration",
        action="reload_toolbox",
        conditions=dict(method=["PUT"])
    )
    webapp.mapper.resource('configuration', 'configuration', path_prefix='/api')
    webapp.mapper.connect("configuration_version",
                          "/api/version", controller="configuration",
                          action="version", conditions=dict(method=["GET"]))
    webapp.mapper.connect("api_whoami",
                          "/api/whoami", controller='configuration',
                          action='whoami',
                          conditions=dict(method=["GET"]))
    webapp.mapper.connect("api_decode",
                          "/api/configuration/decode/{encoded_id}", controller='configuration',
                          action='decode_id',
                          conditions=dict(method=["GET"]))
    webapp.mapper.resource('datatype',
                           'datatypes',
                           path_prefix='/api',
                           collection={'sniffers': 'GET', 'mapping': 'GET', 'converters': 'GET', 'edam_data': 'GET', 'edam_formats': 'GET'},
                           parent_resources=dict(member_name='datatype', collection_name='datatypes'))
    webapp.mapper.resource('search', 'search', path_prefix='/api')
    webapp.mapper.resource('page', 'pages', path_prefix="/api")
    webapp.mapper.resource('revision', 'revisions',
                           path_prefix='/api/pages/{page_id}',
                           controller='page_revisions',
                           parent_resources=dict(member_name='page', collection_name='pages'))

    webapp.mapper.connect("history_archive_export",
                          "/api/histories/{id}/exports", controller="histories",
                          action="archive_export", conditions=dict(method=["PUT"]))
    webapp.mapper.connect("history_archive_download",
                          "/api/histories/{id}/exports/{jeha_id}", controller="histories",
                          action="archive_download", conditions=dict(method=["GET"]))

    webapp.mapper.connect('/api/histories/{history_id}/contents/archive',
                          controller='history_contents', action='archive')
    webapp.mapper.connect('/api/histories/{history_id}/contents/archive/{filename}{.format}',
                          controller='history_contents', action='archive')
    webapp.mapper.connect("/api/histories/{history_id}/contents/dataset_collections/{id}/download",
                          controller='history_contents',
                          action='download_dataset_collection',
                          conditions=dict(method=["GET"]))
    webapp.mapper.connect("/api/dataset_collections/{id}/download",
                          controller='history_contents',
                          action='download_dataset_collection',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect("/api/histories/{history_id}/jobs_summary",
                          action="index_jobs_summary",
                          controller='history_contents',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect("/api/histories/{history_id}/contents/{type:%s}s/{id}/jobs_summary" % "|".join(valid_history_contents_types),
                          action="show_jobs_summary",
                          controller='history_contents',
                          conditions=dict(method=["GET"]))
    # ---- visualizations registry ---- generic template renderer
    # @deprecated: this route should be considered deprecated
    webapp.add_route('/visualization/show/{visualization_name}', controller='visualization', action='render', visualization_name=None)

    # provide an alternate route to visualization plugins that's closer to their static assets
    # (/plugins/visualizations/{visualization_name}/static) and allow them to use relative urls to those
    webapp.mapper.connect('visualization_plugin', '/plugins/visualizations/{visualization_name}/show',
        controller='visualization', action='render')
    webapp.mapper.connect('saved_visualization', '/plugins/visualizations/{visualization_name}/saved',
        controller='visualization', action='saved')
    # same with IE's
    webapp.mapper.connect('interactive_environment_plugin', '/plugins/interactive_environments/{visualization_name}/show',
        controller='visualization', action='render')
    webapp.mapper.connect('saved_interactive_environment', '/plugins/interactive_environments/{visualization_name}/saved',
        controller='visualization', action='saved')

    # Deprecated in favor of POST /api/workflows with 'workflow' in payload.
    webapp.mapper.connect('import_workflow_deprecated',
                          '/api/workflows/upload',
                          controller='workflows',
                          action='import_new_workflow_deprecated',
                          conditions=dict(method=['POST']))
    webapp.mapper.connect('workflow_dict',
                          '/api/workflows/{workflow_id}/download',
                          controller='workflows',
                          action='workflow_dict',
                          conditions=dict(method=['GET']))
    # Preserve the following download route for now for dependent applications  -- deprecate at some point
    webapp.mapper.connect('workflow_dict',
                          '/api/workflows/download/{workflow_id}',
                          controller='workflows',
                          action='workflow_dict',
                          conditions=dict(method=['GET']))
    # Deprecated in favor of POST /api/workflows with shared_workflow_id in payload.
    webapp.mapper.connect('import_shared_workflow_deprecated',
                          '/api/workflows/import',
                          controller='workflows',
                          action='import_shared_workflow_deprecated',
                          conditions=dict(method=['POST']))

    # route for creating/getting converted datasets
    webapp.mapper.connect('/api/datasets/{dataset_id}/converted', controller='datasets', action='converted', ext=None)
    webapp.mapper.connect('/api/datasets/{dataset_id}/converted/{ext}', controller='datasets', action='converted')

    # API refers to usages and invocations - these mean the same thing but the
    # usage routes should be considered deprecated.
    invoke_names = {
        "invocations": "",
        "usage": "_deprecated",
    }
    for noun, suffix in invoke_names.items():
        name = "%s%s" % (noun, suffix)
        webapp.mapper.connect(
            'list_workflow_%s' % name,
            '/api/workflows/{workflow_id}/%s' % noun,
            controller='workflows',
            action='index_invocations',
            conditions=dict(method=['GET'])
        )

        webapp.mapper.connect(
            'workflow_%s_contents' % name,
            '/api/workflows/{workflow_id}/%s/{invocation_id}' % noun,
            controller='workflows',
            action='show_invocation',
            conditions=dict(method=['GET'])
        )

        webapp.mapper.connect(
            'cancel_workflow_%s' % name,
            '/api/workflows/{workflow_id}/%s/{invocation_id}' % noun,
            controller='workflows',
            action='cancel_invocation',
            conditions=dict(method=['DELETE'])
        )

        webapp.mapper.connect(
            'workflow_%s_step' % name,
            '/api/workflows/{workflow_id}/%s/{invocation_id}/steps/{step_id}' % noun,
            controller='workflows',
            action='invocation_step',
            conditions=dict(method=['GET'])
        )

        webapp.mapper.connect(
            'workflow_%s_step_update' % name,
            '/api/workflows/{workflow_id}/%s/{invocation_id}/steps/{step_id}' % noun,
            controller='workflows',
            action='update_invocation_step',
            conditions=dict(method=['PUT'])
        )

        webapp.mapper.connect(
            'workflow_%s' % name,
            '/api/workflows/{workflow_id}/%s' % noun,
            controller='workflows',
            action='invoke',
            conditions=dict(method=['POST'])
        )
    # ============================
    # ===== AUTHENTICATE API =====
    # ============================

    webapp.mapper.connect('api_key_retrieval',
                          '/api/authenticate/baseauth/',
                          controller='authenticate',
                          action='get_api_key',
                          conditions=dict(method=["GET"]))

    # =====================
    # ===== TOURS API =====
    # =====================

    webapp.mapper.connect('index',
                          '/api/tours',
                          controller='tours',
                          action='index',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('show',
                          '/api/tours/{tour_id}',
                          controller='tours',
                          action='show',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('update_tour',
                          '/api/tours/{tour_id}',
                          controller='tours',
                          action='update_tour',
                          conditions=dict(method=["POST"]))

    # ================================
    # ===== USERS API =====
    # ================================

    webapp.mapper.connect('api_key',
                          '/api/users/{id}/api_key',
                          controller='users',
                          action='api_key',
                          conditions=dict(method=["POST"]))

    webapp.mapper.connect('get_api_key',
                          '/api/users/{id}/api_key/inputs',
                          controller='users',
                          action='get_api_key',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('set_api_key',
                          '/api/users/{id}/api_key/inputs',
                          controller='users',
                          action='set_api_key',
                          conditions=dict(method=["PUT"]))

    webapp.mapper.connect('get_information',
                          '/api/users/{id}/information/inputs',
                          controller='users',
                          action='get_information',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('set_information',
                          '/api/users/{id}/information/inputs',
                          controller='users',
                          action='set_information',
                          conditions=dict(method=["PUT"]))

    webapp.mapper.connect('get_password',
                          '/api/users/{id}/password/inputs',
                          controller='users',
                          action='get_password',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('set_password',
                          '/api/users/{id}/password/inputs',
                          controller='users',
                          action='set_password',
                          conditions=dict(method=["PUT"]))

    webapp.mapper.connect('get_permissions',
                          '/api/users/{id}/permissions/inputs',
                          controller='users',
                          action='get_permissions',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('set_permissions',
                          '/api/users/{id}/permissions/inputs',
                          controller='users',
                          action='set_permissions',
                          conditions=dict(method=["PUT"]))

    webapp.mapper.connect('get_toolbox_filters',
                          '/api/users/{id}/toolbox_filters/inputs',
                          controller='users',
                          action='get_toolbox_filters',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('set_toolbox_filters',
                          '/api/users/{id}/toolbox_filters/inputs',
                          controller='users',
                          action='set_toolbox_filters',
                          conditions=dict(method=["PUT"]))

    webapp.mapper.connect('get_communication',
                          '/api/users/{id}/communication/inputs',
                          controller='users',
                          action='get_communication',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('set_communication',
                          '/api/users/{id}/communication/inputs',
                          controller='users',
                          action='set_communication',
                          conditions=dict(method=["PUT"]))

    webapp.mapper.connect('get_custom_builds',
                          '/api/users/{id}/custom_builds',
                          controller='users',
                          action='get_custom_builds',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('add_custom_builds',
                          '/api/users/{id}/custom_builds/{key}',
                          controller='users',
                          action='add_custom_builds',
                          conditions=dict(method=["PUT"]))

    webapp.mapper.connect('delete_custom_builds',
                          '/api/users/{id}/custom_builds/{key}',
                          controller='users',
                          action='delete_custom_builds',
                          conditions=dict(method=["DELETE"]))

    # ========================
    # ===== WEBHOOKS API =====
    # ========================

    webapp.mapper.connect('get_all_webhooks',
                          '/api/webhooks',
                          controller='webhooks',
                          action='all_webhooks',
                          conditions=dict(method=['GET']))

    webapp.mapper.connect('get_webhook_data',
                          '/api/webhooks/{webhook_id}/data',
                          controller='webhooks',
                          action='webhook_data',
                          conditions=dict(method=['GET']))

    # =======================
    # ===== LIBRARY API =====
    # =======================

    webapp.mapper.connect('update_library',
                          '/api/libraries/{id}',
                          controller='libraries',
                          action='update',
                          conditions=dict(method=["PATCH", "PUT"]))

    webapp.mapper.connect('show_library_permissions',
                          '/api/libraries/{encoded_library_id}/permissions',
                          controller='libraries',
                          action='get_permissions',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('set_library_permissions',
                          '/api/libraries/{encoded_library_id}/permissions',
                          controller='libraries',
                          action='set_permissions',
                          conditions=dict(method=["POST"]))

    webapp.mapper.connect('show_ld_item',
                          '/api/libraries/datasets/{id}',
                          controller='library_datasets',
                          action='show',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('load_ld',
                          '/api/libraries/datasets/',
                          controller='library_datasets',
                          action='load',
                          conditions=dict(method=["POST"]))

    webapp.mapper.connect('show_version_of_ld_item',
                          '/api/libraries/datasets/{encoded_dataset_id}/versions/{encoded_ldda_id}',
                          controller='library_datasets',
                          action='show_version',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('update_ld',
                          '/api/libraries/datasets/{encoded_dataset_id}',
                          controller='library_datasets',
                          action='update',
                          conditions=dict(method=["PATCH"]))

    webapp.mapper.connect('show_legitimate_ld_roles',
                          '/api/libraries/datasets/{encoded_dataset_id}/permissions',
                          controller='library_datasets',
                          action='show_roles',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('update_ld_permissions',
                          '/api/libraries/datasets/{encoded_dataset_id}/permissions',
                          controller='library_datasets',
                          action='update_permissions',
                          conditions=dict(method=["POST"]))

    webapp.mapper.connect('delete_ld_item',
                          '/api/libraries/datasets/{encoded_dataset_id}',
                          controller='library_datasets',
                          action='delete',
                          conditions=dict(method=["DELETE"]))

    webapp.mapper.connect('download_ld_items',
                          '/api/libraries/datasets/download/{format}',
                          controller='library_datasets',
                          action='download',
                          conditions=dict(method=["POST", "GET"]))

    webapp.mapper.resource_with_deleted('library',
                                        'libraries',
                                        path_prefix='/api')

    webapp.mapper.resource('content',
                           'contents',
                           controller='library_contents',
                           name_prefix='library_',
                           path_prefix='/api/libraries/{library_id}',
                           parent_resources=dict(member_name='library', collection_name='libraries'))

    _add_item_extended_metadata_controller(webapp,
                                           name_prefix="library_dataset_",
                                           path_prefix='/api/libraries/{library_id}/contents/{library_content_id}')

    # =======================
    # ===== FOLDERS API =====
    # =======================

    webapp.mapper.connect('add_history_datasets_to_library',
                          '/api/folders/{encoded_folder_id}/contents',
                          controller='folder_contents',
                          action='create',
                          conditions=dict(method=["POST"]))

    webapp.mapper.connect('create_folder',
                          '/api/folders/{encoded_parent_folder_id}',
                          controller='folders',
                          action='create',
                          conditions=dict(method=["POST"]))

    webapp.mapper.connect('delete_folder',
                          '/api/folders/{encoded_folder_id}',
                          controller='folders',
                          action='delete',
                          conditions=dict(method=["DELETE"]))

    webapp.mapper.connect('update_folder',
                          '/api/folders/{encoded_folder_id}',
                          controller='folders',
                          action='update',
                          conditions=dict(method=["PATCH", "PUT"]))

    webapp.mapper.resource('folder',
                           'folders',
                           path_prefix='/api')

    webapp.mapper.connect('show_folder_permissions',
                          '/api/folders/{encoded_folder_id}/permissions',
                          controller='folders',
                          action='get_permissions',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('set_folder_permissions',
                          '/api/folders/{encoded_folder_id}/permissions',
                          controller='folders',
                          action='set_permissions',
                          conditions=dict(method=["POST"]))

    webapp.mapper.resource('content',
                           'contents',
                           controller='folder_contents',
                           name_prefix='folder_',
                           path_prefix='/api/folders/{folder_id}',
                           parent_resources=dict(member_name='folder', collection_name='folders'),
                           conditions=dict(method=["GET"]))

    webapp.mapper.resource('job',
                           'jobs',
                           path_prefix='/api')
    webapp.mapper.connect('job_search', '/api/jobs/search', controller='jobs', action='search', conditions=dict(method=['POST']))
    webapp.mapper.connect('job_inputs', '/api/jobs/{id}/inputs', controller='jobs', action='inputs', conditions=dict(method=['GET']))
    webapp.mapper.connect('job_outputs', '/api/jobs/{id}/outputs', controller='jobs', action='outputs', conditions=dict(method=['GET']))
    webapp.mapper.connect('build_for_rerun', '/api/jobs/{id}/build_for_rerun', controller='jobs', action='build_for_rerun', conditions=dict(method=['GET']))
    webapp.mapper.connect('job_error', '/api/jobs/{id}/error', controller='jobs', action='error', conditions=dict(method=['POST']))

    # Job files controllers. Only for consumption by remote job runners.
    webapp.mapper.resource('file',
                           'files',
                           controller="job_files",
                           name_prefix="job_",
                           path_prefix='/api/jobs/{job_id}',
                           parent_resources=dict(member_name="job", collection_name="jobs"))

    _add_item_extended_metadata_controller(webapp,
                                           name_prefix="history_dataset_",
                                           path_prefix='/api/histories/{history_id}/contents/{history_content_id}')

    # ====================
    # ===== TOOLSHED =====
    # ====================

    # Handle displaying tool help images and README file images contained in repositories installed from the tool shed.
    webapp.add_route('/admin_toolshed/static/images/{repository_id}/{image_file:.+?}',
                     controller='admin_toolshed',
                     action='display_image_in_repository',
                     repository_id=None,
                     image_file=None)

    webapp.mapper.connect('tool_shed_contents',
                          '/api/tool_shed/contents',
                          controller='toolshed',
                          action='show',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('tool_shed_category_contents',
                          '/api/tool_shed/category',
                          controller='toolshed',
                          action='category',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('tool_shed_repository_details',
                          '/api/tool_shed/repository',
                          controller='toolshed',
                          action='repository',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('tool_sheds',
                          '/api/tool_shed',
                          controller='toolshed',
                          action='index',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('tool_shed_search',
                          '/api/tool_shed/search',
                          controller='toolshed',
                          action='search',
                          conditions=dict(method=["GET", "POST"]))

    webapp.mapper.connect('tool_shed_status',
                          '/api/tool_shed/status',
                          controller='toolshed',
                          action='status',
                          conditions=dict(method=["GET", "POST"]))

    webapp.mapper.connect('shed_tool_json',
                          '/api/tool_shed/tool_json',
                          controller='toolshed',
                          action='tool_json',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('tool_shed_repository',
                          '/api/tool_shed_repositories/:id/status',
                          controller='tool_shed_repositories',
                          action='status',
                          conditions=dict(method=["GET"]))

    webapp.mapper.connect('install_repository',
                          '/api/tool_shed_repositories',
                          controller='tool_shed_repositories',
                          action='install_repository_revision',
                          conditions=dict(method=['POST']))

    webapp.mapper.connect('install_repository',
                          '/api/tool_shed_repositories/install',
                          controller='tool_shed_repositories',
                          action='install',
                          conditions=dict(method=['POST']))

    webapp.mapper.connect('tool_shed_repository',
                          '/api/tool_shed_repositories',
                          controller='tool_shed_repositories',
                          action='uninstall_repository',
                          conditions=dict(method=["DELETE"]))

    webapp.mapper.connect('tool_shed_repository',
                          '/api/tool_shed_repositories/{id}',
                          controller='tool_shed_repositories',
                          action='uninstall_repository',
                          conditions=dict(method=["DELETE"]))

    # Galaxy API for tool shed features.
    webapp.mapper.resource('tool_shed_repository',
                           'tool_shed_repositories',
                           member={'repair_repository_revision': 'POST',
                                   'exported_workflows': 'GET',
                                   'import_workflow': 'POST',
                                   'import_workflows': 'POST'},
                           collection={'get_latest_installable_revision': 'POST',
                                       'reset_metadata_on_installed_repositories': 'POST'},
                           controller='tool_shed_repositories',
                           name_prefix='tool_shed_repository_',
                           path_prefix='/api',
                           new={'install_repository_revision': 'POST'},
                           parent_resources=dict(member_name='tool_shed_repository', collection_name='tool_shed_repositories'))

    # ==== Trace/Metrics Logger
    # Connect logger from app
    if app.trace_logger:
        webapp.trace_logger = app.trace_logger

    # metrics logging API
    # webapp.mapper.connect( "index", "/api/metrics",
    #    controller="metrics", action="index", conditions=dict( method=["GET"] ) )
    # webapp.mapper.connect( "show", "/api/metrics/{id}",
    #    controller="metrics", action="show", conditions=dict( method=["GET"] ) )
    webapp.mapper.connect("create", "/api/metrics", controller="metrics",
                          action="create", conditions=dict(method=["POST"]))


def _add_item_tags_controller(webapp, name_prefix, path_prefix, **kwd):
    # Not just using map.resources because actions should be based on name not id
    controller = "%stags" % name_prefix
    name = "%stag" % name_prefix
    path = "%s/tags" % path_prefix
    map = webapp.mapper
    # Allow view items' tags.
    map.connect(name, path,
                controller=controller, action="index",
                conditions=dict(method=["GET"]))
    # Allow remove tag from item
    map.connect("%s_delete" % name, "%s/tags/{tag_name}" % path_prefix,
                controller=controller, action="delete",
                conditions=dict(method=["DELETE"]))
    # Allow create a new tag with from name
    map.connect("%s_create" % name, "%s/tags/{tag_name}" % path_prefix,
                controller=controller, action="create",
                conditions=dict(method=["POST"]))
    # Allow update tag value
    map.connect("%s_update" % name, "%s/tags/{tag_name}" % path_prefix,
                controller=controller, action="update",
                conditions=dict(method=["PUT"]))
    # Allow show tag by name
    map.connect("%s_show" % name, "%s/tags/{tag_name}" % path_prefix,
                controller=controller, action="show",
                conditions=dict(method=["GET"]))


def _add_item_extended_metadata_controller(webapp, name_prefix, path_prefix, **kwd):
    controller = "%sextended_metadata" % name_prefix
    name = "%sextended_metadata" % name_prefix
    webapp.mapper.resource(name, "extended_metadata", path_prefix=path_prefix, controller=controller)


def _add_item_annotation_controller(webapp, name_prefix, path_prefix, **kwd):
    controller = "%sannotations" % name_prefix
    name = "%sannotation" % name_prefix
    webapp.mapper.resource(name, "annotation", path_prefix=path_prefix, controller=controller)


def _add_item_provenance_controller(webapp, name_prefix, path_prefix, **kwd):
    controller = "%sprovenance" % name_prefix
    name = "%sprovenance" % name_prefix
    webapp.mapper.resource(name, "provenance", path_prefix=path_prefix, controller=controller)


def wrap_in_middleware(app, global_conf, application_stack, **local_conf):
    """
    Based on the configuration wrap `app` in a set of common and useful
    middleware.
    """
    webapp = app
    stack = application_stack

    # Merge the global and local configurations
    conf = global_conf.copy()
    conf.update(local_conf)
    debug = asbool(conf.get('debug', False))
    # First put into place httpexceptions, which must be most closely
    # wrapped around the application (it can interact poorly with
    # other middleware):
    app = wrap_if_allowed(app, stack, httpexceptions.make_middleware, name='paste.httpexceptions', args=(conf,))
    # Statsd request timing and profiling
    statsd_host = conf.get('statsd_host', None)
    if statsd_host:
        from galaxy.web.framework.middleware.statsd import StatsdMiddleware
        app = wrap_if_allowed(app, stack, StatsdMiddleware,
                              args=(statsd_host,
                                    conf.get('statsd_port', 8125),
                                    conf.get('statsd_prefix', 'galaxy'),
                                    conf.get('statsd_influxdb', False)))
        log.debug("Enabling 'statsd' middleware")
    # graphite request timing and profiling
    graphite_host = conf.get('graphite_host', None)
    if graphite_host:
        from galaxy.web.framework.middleware.graphite import GraphiteMiddleware
        app = wrap_if_allowed(app, stack, GraphiteMiddleware,
                              args=(graphite_host,
                                    conf.get('graphite_port', 2003),
                                    conf.get('graphite_prefix', 'galaxy')))
        log.debug("Enabling 'graphite' middleware")
    # If we're using remote_user authentication, add middleware that
    # protects Galaxy from improperly configured authentication in the
    # upstream server
    single_user = conf.get('single_user', None)
    use_remote_user = asbool(conf.get('use_remote_user', False)) or single_user
    if use_remote_user:
        from galaxy.web.framework.middleware.remoteuser import RemoteUser
        app = wrap_if_allowed(app, stack, RemoteUser,
                              kwargs=dict(
                                  maildomain=conf.get('remote_user_maildomain', None),
                                  display_servers=util.listify(conf.get('display_servers', '')),
                                  single_user=single_user,
                                  admin_users=conf.get('admin_users', '').split(','),
                                  remote_user_header=conf.get('remote_user_header', 'HTTP_REMOTE_USER'),
                                  remote_user_secret_header=conf.get('remote_user_secret', None),
                                  normalize_remote_user_email=conf.get('normalize_remote_user_email', False)))
    # The recursive middleware allows for including requests in other
    # requests or forwarding of requests, all on the server side.
    if asbool(conf.get('use_recursive', True)):
        from paste import recursive
        app = wrap_if_allowed(app, stack, recursive.RecursiveMiddleware, args=(conf,))
    # If sentry logging is enabled, log here before propogating up to
    # the error middleware
    sentry_dsn = conf.get('sentry_dsn', None)
    sentry_sloreq = float(conf.get('sentry_sloreq_threshold', 0))
    if sentry_dsn:
        from galaxy.web.framework.middleware.sentry import Sentry
        app = wrap_if_allowed(app, stack, Sentry, args=(sentry_dsn, sentry_sloreq))
    # Various debug middleware that can only be turned on if the debug
    # flag is set, either because they are insecure or greatly hurt
    # performance
    if debug:
        # Middleware to check for WSGI compliance
        if asbool(conf.get('use_lint', False)):
            from paste import lint
            app = wrap_if_allowed(app, stack, lint.make_middleware, name='paste.lint', args=(conf,))
        # Middleware to run the python profiler on each request
        if asbool(conf.get('use_profile', False)):
            from paste.debug import profile
            app = wrap_if_allowed(app, stack, profile.ProfileMiddleware, args=(conf,))
    if debug and asbool(conf.get('use_interactive', False)):
        # Interactive exception debugging, scary dangerous if publicly
        # accessible, if not enabled we'll use the regular error printing
        # middleware.
        try:
            from weberror import evalexception
            app = wrap_if_allowed_or_fail(app, stack, evalexception.EvalException,
                                          args=(conf,),
                                          kwargs=dict(templating_formatters=build_template_error_formatters()))
        except MiddlewareWrapUnsupported as exc:
            log.warning(str(exc))
            import galaxy.web.framework.middleware.error
            app = wrap_if_allowed(app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,))
    else:
        # Not in interactive debug mode, just use the regular error middleware
        import galaxy.web.framework.middleware.error
        app = wrap_if_allowed(app, stack, galaxy.web.framework.middleware.error.ErrorMiddleware, args=(conf,))
    # Transaction logging (apache access.log style)
    if asbool(conf.get('use_translogger', True)):
        from galaxy.web.framework.middleware.translogger import TransLogger
        app = wrap_if_allowed(app, stack, TransLogger)
    # X-Forwarded-Host handling
    from galaxy.web.framework.middleware.xforwardedhost import XForwardedHostMiddleware
    app = wrap_if_allowed(app, stack, XForwardedHostMiddleware)
    # Request ID middleware
    from galaxy.web.framework.middleware.request_id import RequestIDMiddleware
    app = wrap_if_allowed(app, stack, RequestIDMiddleware)
    # api batch call processing middleware
    from galaxy.web.framework.middleware.batch import BatchMiddleware
    app = wrap_if_allowed(app, stack, BatchMiddleware, args=(webapp, {}))
    if asbool(conf.get('enable_per_request_sql_debugging', False)):
        from galaxy.web.framework.middleware.sqldebug import SQLDebugMiddleware
        app = wrap_if_allowed(app, stack, SQLDebugMiddleware, args=(webapp, {}))
    return app


def wrap_in_static(app, global_conf, plugin_frameworks=None, **local_conf):
    urlmap, cache_time = galaxy.web.framework.webapp.build_url_map(app, global_conf, local_conf)
    return urlmap
